package vpc.ext.stacksize;

import java.util.*;
import vpc.core.decl.Method;
import java.io.*;
import vpc.ext.stacksize.util.DataHandler;
import vpc.ext.stacksize.util.VstackException;
import vpc.ext.stacksize.visitors.*;

public class Node {
	
	//************* DATA *******************
	
	private static int idCounter = -1;
	public final HashMap<String, Edge> edges;
	
	public final String name;
	public int stackPush;
	public Node root;
	public final int id;
	
	//	************* STATIC CLASS METHODS *******************
	
	public static Node constructMethodNode(Method entry) {
		
		return new Node(entry.getFullName(), DataHandler.getStackPush(entry));		
	}
	
	
	
	private static int getNewId() {
		idCounter++;
		return idCounter;
	}
	
	public static void pruneNode(Node n) {
		if (n == null) {
			return;
		}
		for (Edge e : n.edges.values()) {
			e = null;
		}
		n = null;
		return;
	}
	
	//	************* CONSTRUCTORS *******************
	
	public Node(String name, int stackPush)
	{
		this.id = Node.getNewId();
		edges = new HashMap<String, Edge>();
		this.name = name;
		this.stackPush = stackPush;
		root = this;
	}
	
	public Node(String name, int stackPush, Node root)
	{
		this.id = Node.getNewId();
		edges = new HashMap<String, Edge>();
		this.name = name;
		this.stackPush = stackPush;
		this.root = root;
	}
	
	//	************* OBJECT METHODS *******************
	
	public void addEdge(Node n)
	{
		if (n == null) {
			return;
		}
		if(!edges.containsKey(n.name))
			this.edges.put(n.name, new Edge(this, n, n.stackPush));
	}
	
	public void removeEdge(Node n) {
		if (n == null) {
			return;
		}
		
		if (this.edges.containsKey(n.name)) {
			Edge e = this.edges.remove(n.name);
			e = null;
		}
		return;
	}
	
	public void changeRoot(Node root, Set<String> nodesSeen)
	{
		if(nodesSeen.contains(this.name))
			return;
		
		nodesSeen.add(this.name);
		
		this.root = root;
		
		for(Edge e: edges.values())
			e.changeRoot(root, nodesSeen);
	}
	

	
	/**
	 * Function to find a graph node by its string name.
	 * @param name The search string. Ex: the string returned by Method.getFullName()
	 * @param nodesSeen A list of Node(s) already discovered in the graph traversal.
	 * @return The Node matching the name (if found), null otherwise.
	 */
	public Node find(String searchName, Set<Node> nodesSeen)
	{
		//If this Node matches the name parameter, then return this
		if(this.name.equals(searchName))
			return this;
		
		//If this Node is part of the nodesSeen list, then a cycle exists.
		if(nodesSeen.contains(this))
			return null;
		else
			nodesSeen.add(this);
		// Else this Node has not been processed, add it.
		
		// Search each of the Edge(s) of the Node for a match as well.
		for(Edge e: edges.values())
		{
			Node result = e.find(searchName, nodesSeen);
			
			if(result != null)
				return result;
		}		
		
		return null;
	}
	
	public Node find(int searchId, Set<Node> nodesSeen) {
		
		//If this Node matches the name parameter, then return this
		if(this.id == searchId)
			return this;
		
		//If this Node is part of the nodesSeen list, then a cycle exists.
		if(nodesSeen.contains(this))
			return null;
		else
			nodesSeen.add(this);
		// Else this Node has not been processed, add it.
		
		// Search each of the Edge(s) of the Node for a match as well.
		for(Edge e: edges.values())
		{
			Node result = e.find(searchId, nodesSeen);
			
			if(result != null)
				return result;
		}		
		
		return null;
	}
	
	/**
	 * 
	 * @return A String representation of the Edge set of this Node.
	 */
	public String edgesToString()
	{	
		StringBuffer buff = new java.lang.StringBuffer();
		for (Edge e : edges.values()) {
			buff.append(e.id);
			buff.append("\n\t");
		}
		return buff.toString();
	}
	
	public String toString()
	{
		StringBuffer buff = new java.lang.StringBuffer();
		buff.append("NODE : ");
		buff.append(this.name);
		buff.append("\n\t");
		buff.append(this.edgesToString());
		
		return buff.toString();
	}
	
	public void printGraph(PrintStream o, Set<Node> nodesSeen)
	{
		if(nodesSeen.contains(this.name))
			return;
		
		nodesSeen.add(this);
		
		o.println(this.toString());
		
		for(Edge e: edges.values())
			e.printGraph(o, nodesSeen);
	}
	
	/**
	 * This method returns the maximum stack size, which can be thought
	 * of as the sum of the edges of the longest path in the graph.
	 * @param nodesSeen A set of Nodes already seen on the path.
	 * @return The maximum stack push.
	 * @throws CycleException If a cycle is detected in the call graph.
	 */
	public int getMaxPush(Set<Node> nodesSeen) throws VstackException.CycleException
	{   
		int highestPush = 0;
		if(nodesSeen.contains(this)) throw new VstackException.CycleException();
		
		nodesSeen.add(this);
		
		for(Edge e: edges.values())		
			highestPush = Math.max(highestPush, e.getMaxPush(nodesSeen));
		
		
		nodesSeen.remove(this);
		
		return stackPush + highestPush;
		
	}
}
